#include "ExecutionEngine/fpga_sim_api.h"
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <regex>
#include <string>

void sha1_run(uint8_t *input_allocated, uint8_t *input_aligned,
              intptr_t input_offset, intptr_t input_size, intptr_t input_stride,
              uint32_t plain_len, uint8_t *output_allocated,
              uint8_t *output_aligned, intptr_t output_offset,
              intptr_t output_size, intptr_t output_stride) {
  char algo_name[4];
  memcpy(algo_name, "sha1", 4);
  algo_run(algo_name, input_allocated, plain_len, output_allocated);
  return;
}
// ==========================
// Sha1 deciphering function
// ==========================
MemRef1D sha1Crack_run(uint8_t *hash_allocated, uint8_t *hash_aligned,
                       intptr_t hash_offset, intptr_t hash_size,
                       intptr_t hash_stride, uint8_t *input_allocated,
                       uint8_t *input_aligned, intptr_t input_offset,
                       intptr_t input_size, intptr_t input_stride,
                       uint32_t msg_num, uint32_t msg_len) {
  // Calculate the sha-1 value based on the given dictionary input according
  // to the data length msg-len, and compare it with the hash. If they are the
  // same, return the value.
  vector<uint32_t> tmpHashResult(5);
  char algo_name[4];
  memcpy(algo_name, "sha1", 4);
  // TmpMemRef is used to store the returned results
  MemRef1D tmpMemRef;
  // 1. Convert hash to numerical value
  vector<uint32_t> hashSplited(5);
  for (size_t i = 0; (i + 4) <= 20; i += 4) {
    uint32_t tempNum;
    tempNum = (hash_allocated[i] << 24) | (hash_allocated[i + 1] << 16) |
              (hash_allocated[i + 2] << 8) | (hash_allocated[i + 3]);
    hashSplited[i / 4] = tempNum;
  }

  // 2. Retrieve characters from the dictionary and calculate the sha-1 value
  vector<uint8_t> tmpPlain(msg_len);
  for (size_t i = 0; i < msg_num * msg_len; i += msg_len) {
    // Retrieve characters from the dictionary
    for (size_t j = 0; j < msg_len; j++) {
      tmpPlain[j] = input_allocated[i + j];
    }
    // Calculate sha-1 value
    algo_run(algo_name, tmpPlain.data(), tmpPlain.size(),
             reinterpret_cast<uint8_t *>(tmpHashResult.data()));
    // Compared to hashSplit
    if ((tmpHashResult[0] == hashSplited[0]) &&
        (tmpHashResult[1] == hashSplited[1]) &&
        (tmpHashResult[2] == hashSplited[2]) &&
        (tmpHashResult[3] == hashSplited[3]) &&
        (tmpHashResult[4] == hashSplited[4])) {
      // Indicates successful matching of sha-1 value and returns the plaintext
      // value of the match
      tmpMemRef.allocated = input_allocated + i;
      tmpMemRef.aligned = tmpMemRef.allocated;
      tmpMemRef.offset = 0;
      tmpMemRef.sizes = msg_len;
      tmpMemRef.strides = 1;
      return tmpMemRef;
    }
  }
  // Return the original hsah value, indicating that plaintext has been found
  tmpMemRef.allocated = hash_aligned;
  tmpMemRef.aligned = tmpMemRef.allocated;
  tmpMemRef.offset = 0;
  tmpMemRef.sizes = msg_len;
  tmpMemRef.strides = 1;
  return tmpMemRef;
}

void md5_run(uint8_t *input_allocated, uint8_t *input_aligned,
             intptr_t input_offset, intptr_t input_size, intptr_t input_stride,
             uint32_t plain_len, uint8_t *output_allocated,
             uint8_t *output_aligned, intptr_t output_offset,
             intptr_t output_size, intptr_t output_stride) {
  char algo_name[3];
  memcpy(algo_name, "md5", 3);
  algo_run(algo_name, input_allocated, plain_len, output_allocated);
  return;
}

// ==========================
// MD5 deciphering function
// ==========================
MemRef1D md5Crack_run(uint8_t *hash_allocated, uint8_t *hash_aligned,
                      intptr_t hash_offset, intptr_t hash_size,
                      intptr_t hash_stride, uint8_t *input_allocated,
                      uint8_t *input_aligned, intptr_t input_offset,
                      intptr_t input_size, intptr_t input_stride,
                      uint32_t msg_num, uint32_t msg_len) {
  // Calculate the md5 value based on the given dictionary input according
  // to the data length msg-len, and compare it with the hash. If they are the
  // same, return the value.
  vector<uint32_t> tmpHashResult(4);
  char algo_name[3];
  memcpy(algo_name, "md5", 3);
  // TmpMemRef is used to store the returned results
  MemRef1D tmpMemRef;
  // 1. Convert hash to numerical value
  vector<uint32_t> hashSplited(4);
  for (size_t i = 0; (i + 4) <= 16; i += 4) {
    uint32_t tempNum;
    tempNum = (hash_allocated[i + 3] << 24) | (hash_allocated[i + 2] << 16) |
              (hash_allocated[i + 1] << 8) | (hash_allocated[i]);
    hashSplited[i / 4] = tempNum;
  }

  // 2. Retrieve characters from the dictionary and calculate the md5 value
  vector<uint8_t> tmpPlain(msg_len);
  for (size_t i = 0; i < msg_num * msg_len; i += msg_len) {
    // Retrieve characters from the dictionary
    for (size_t j = 0; j < msg_len; j++) {
      tmpPlain[j] = input_allocated[i + j];
    }
    // Calculate md5 value
    algo_run(algo_name, tmpPlain.data(), tmpPlain.size(),
             reinterpret_cast<uint8_t *>(tmpHashResult.data()));
    // Compared to hashSplit
    if ((tmpHashResult[0] == hashSplited[0]) &&
        (tmpHashResult[1] == hashSplited[1]) &&
        (tmpHashResult[2] == hashSplited[2]) &&
        (tmpHashResult[3] == hashSplited[3])) {
      // Indicates successful matching of md5 value and returns the plaintext
      // value of the match
      tmpMemRef.allocated = input_allocated + i;
      tmpMemRef.aligned = tmpMemRef.allocated;
      tmpMemRef.offset = 0;
      tmpMemRef.sizes = msg_len;
      tmpMemRef.strides = 1;
      return tmpMemRef;
    }
  }
  // Return the original hsah value, indicating that plaintext has been found
  tmpMemRef.allocated = hash_aligned;
  tmpMemRef.aligned = tmpMemRef.allocated;
  tmpMemRef.offset = 0;
  tmpMemRef.sizes = msg_len;
  tmpMemRef.strides = 1;
  return tmpMemRef;
}

void sm3_run(uint8_t *input_allocated, uint8_t *input_aligned,
             intptr_t input_offset, intptr_t input_size, intptr_t input_stride,
             uint32_t plain_len, uint8_t *output_allocated,
             uint8_t *output_aligned, intptr_t output_offset,
             intptr_t output_size, intptr_t output_stride) {
  char algo_name[3];
  memcpy(algo_name, "sm3", 3);
  algo_run(algo_name, input_allocated, plain_len, output_allocated);
  return;
}

// ==========================
// sm3 deciphering function
// ==========================
MemRef1D sm3Crack_run(uint8_t *hash_allocated, uint8_t *hash_aligned,
                      intptr_t hash_offset, intptr_t hash_size,
                      intptr_t hash_stride, uint8_t *input_allocated,
                      uint8_t *input_aligned, intptr_t input_offset,
                      intptr_t input_size, intptr_t input_stride,
                      uint32_t msg_num, uint32_t msg_len) {
  // Calculate the sm3 value based on the given dictionary input according
  // to the data length msg-len, and compare it with the hash. If they are the
  // same, return the value.
  vector<uint32_t> tmpHashResult(8);
  char algo_name[3];
  memcpy(algo_name, "sm3", 3);
  // TmpMemRef is used to store the returned results
  MemRef1D tmpMemRef;
  // 1. Convert hash to numerical value
  vector<uint32_t> hashSplited(8);
  for (size_t i = 0; (i + 4) <= 32; i += 4) {
    uint32_t tempNum;
    tempNum = (hash_allocated[i] << 24) | (hash_allocated[i + 1] << 16) |
              (hash_allocated[i + 2] << 8) | (hash_allocated[i + 3]);
    hashSplited[i / 4] = tempNum;
  }

  // 2. Retrieve characters from the dictionary and calculate the sm3 value
  vector<uint8_t> tmpPlain(msg_len);
  for (size_t i = 0; i < msg_num * msg_len; i += msg_len) {
    // Retrieve characters from the dictionary
    for (size_t j = 0; j < msg_len; j++) {
      tmpPlain[j] = input_allocated[i + j];
    }
    // Calculate sm3 value
    algo_run(algo_name, tmpPlain.data(), tmpPlain.size(),
             reinterpret_cast<uint8_t *>(tmpHashResult.data()));
    // Compared to hashSplit
    if ((tmpHashResult[0] == hashSplited[0]) &&
        (tmpHashResult[1] == hashSplited[1]) &&
        (tmpHashResult[2] == hashSplited[2]) &&
        (tmpHashResult[3] == hashSplited[3]) &&
        (tmpHashResult[4] == hashSplited[4]) &&
        (tmpHashResult[5] == hashSplited[5]) &&
        (tmpHashResult[6] == hashSplited[6]) &&
        (tmpHashResult[7] == hashSplited[7])) {
      // Indicates successful matching of sm3 value and returns the plaintext
      // value of the match
      tmpMemRef.allocated = input_allocated + i;
      tmpMemRef.aligned = tmpMemRef.allocated;
      tmpMemRef.offset = 0;
      tmpMemRef.sizes = msg_len;
      tmpMemRef.strides = 1;
      return tmpMemRef;
    }
  }
  // Return the original hsah value, indicating that plaintext has been found
  tmpMemRef.allocated = hash_aligned;
  tmpMemRef.aligned = tmpMemRef.allocated;
  tmpMemRef.offset = 0;
  tmpMemRef.sizes = msg_len;
  tmpMemRef.strides = 1;
  return tmpMemRef;
}

void aes_run(uint8_t *input_allocated, uint8_t *input_aligned,
             intptr_t input_offset, intptr_t input_size, intptr_t input_stride,
             uint32_t plain_len, uint8_t *output_allocated,
             uint8_t *output_aligned, intptr_t output_affset,
             intptr_t output_size, intptr_t output_stride,
             uint8_t *key_allocated, uint8_t *key_aligned, intptr_t key_offset,
             intptr_t key_size, intptr_t key_stride,
             uint8_t *sBoxBase_allocated, uint8_t *sBoxBase_aligned,
             intptr_t sBoxBase_offset, intptr_t sBoxBase_size,
             intptr_t sBoxBase_stride) {
  char algo_name[3];
  memcpy(algo_name, "aes", 3);
  algo_aes_run(algo_name, input_allocated, plain_len, output_allocated,
               key_allocated);
}

void aesCrack_run(uint8_t *input_allocated, uint8_t *input_aligned,
                  intptr_t input_offset, intptr_t input_size,
                  intptr_t input_stride, uint32_t plain_len,
                  uint8_t *output_allocated, uint8_t *output_aligned,
                  intptr_t output_affset, intptr_t output_size,
                  intptr_t output_stride, uint32_t cptLen,
                  uint8_t *key_allocated, uint8_t *key_aligned,
                  intptr_t key_offset, intptr_t key_size, intptr_t key_stride,
                  uint32_t keyLen, uint32_t msgCnt) {
  char algo_name[8];
  memcpy(algo_name, "aesCrack", 8);
  algo_aes_run(algo_name, input_allocated, plain_len, output_allocated,
               key_allocated);
}

void algo_aes_run(char *algo_name, uint8_t *plain_addr, uint32_t plain_len,
                  uint8_t *cipher_addr, uint8_t *key) {
  if (strcmp(algo_name, "aes") == 0) {
    // aes encryption
    fpga_aes128(plain_addr, plain_len, key, cipher_addr);
  } else if (strcmp(algo_name, "aesCrack") == 0) {
    // aes decryption
    fpga_aes128Crack(plain_addr, plain_len, key, cipher_addr);
  } else {
    cerr << algo_name << " is not yet supported" << "." << endl;
    exit(1);
  }
}

void algo_run(char *algo_name, uint8_t *plain_addr, uint32_t plain_len,
              uint8_t *cipher_addr) {

  if (strcmp(algo_name, "sm3") == 0) {
    fpga_sm3(plain_addr, plain_len, cipher_addr);
  } else if (strcmp(algo_name, "sha1") == 0) {
    fpga_sha1(plain_addr, plain_len, cipher_addr);
  } else if (strcmp(algo_name, "md5") == 0) {
    fpga_md5(plain_addr, plain_len, cipher_addr);
  } else {
    cerr << algo_name << " is not yet supported" << "." << endl;
    exit(1);
  }
}